"""
Configuration management and environment settings for document analysis module.

Provides centralized configuration loading, validation, and environment-specific
settings management with support for different deployment environments.
"""

import json
import os
from pathlib import Path
from typing import Dict, Any, Optional, Union, List
from dataclasses import dataclass, field
import logging
from enum import Enum

logger = logging.getLogger(__name__)


class Environment(Enum):
    """Supported deployment environments."""
    DEVELOPMENT = "development"
    TESTING = "testing"
    PRODUCTION = "production"


@dataclass
class DatabaseConfig:
    """Database configuration settings."""
    type: str = "sqlite"
    path: str = "data/analysis_cache.db"
    backup_enabled: bool = True
    backup_interval_hours: int = 24
    connection_timeout: int = 30
    max_connections: int = 10


@dataclass
class AnalysisConfig:
    """Analysis engine configuration."""
    default_language: str = "zh"
    cache_enabled: bool = True
    max_file_size_mb: int = 50
    timeout_seconds: int = 30
    supported_formats: List[str] = field(default_factory=lambda: ["markdown", "text", "pdf", "word"])
    nlp_model: str = "zh_core_web_sm"
    max_concurrent_analyses: int = 4


@dataclass
class ScoringConfig:
    """Scoring and evaluation configuration."""
    weights: Dict[str, float] = field(default_factory=lambda: {
        "readability": 0.25,
        "completeness": 0.20,
        "consistency": 0.20,
        "specificity": 0.20,
        "structure": 0.15
    })
    thresholds: Dict[str, float] = field(default_factory=lambda: {
        "excellent": 85,
        "good": 70,
        "fair": 55,
        "poor": 0
    })


@dataclass
class PerformanceConfig:
    """Performance and resource management configuration."""
    memory_limit_mb: int = 400
    cache_size: int = 100
    cleanup_threshold_percent: int = 80
    max_processing_time_seconds: int = 300


@dataclass
class LoggingConfig:
    """Logging configuration."""
    level: str = "INFO"
    file: str = "logs/document_analyzer.log"
    max_size_mb: int = 10
    backup_count: int = 5
    format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    structured_logging: bool = True


@dataclass
class UIConfig:
    """User interface configuration."""
    theme: str = "default"
    window_size: List[int] = field(default_factory=lambda: [1200, 800])
    auto_save: bool = True
    progress_update_interval_ms: int = 500
    font_size: int = 10


@dataclass
class SecurityConfig:
    """Security and validation configuration."""
    validate_file_types: bool = True
    scan_uploads: bool = False
    max_path_length: int = 260
    allowed_extensions: List[str] = field(default_factory=lambda: [".md", ".txt", ".pdf", ".doc", ".docx"])


@dataclass
class OutputConfig:
    """Output and reporting configuration."""
    default_format: str = "html"
    include_metrics: bool = True
    include_suggestions: bool = True
    include_clarifications: bool = True
    output_directory: str = "reports/"
    template_directory: str = "templates/"


class SettingsManager:
    """Centralized settings and configuration manager."""

    def __init__(self, config_file: Union[str, Path] = None, environment: Environment = None):
        """Initialize settings manager.

        Args:
            config_file: Path to configuration file
            environment: Deployment environment
        """
        self.config_file = Path(config_file) if config_file else Path("config.json")
        self.environment = environment or self._detect_environment()
        self._config_cache = {}
        self._load_configuration()

    def _detect_environment(self) -> Environment:
        """Auto-detect current environment.

        Returns:
            Detected environment
        """
        # Check environment variable
        env_var = os.getenv("DOCS_ANALYSIS_ENV", "").lower()
        if env_var:
            try:
                return Environment(env_var)
            except ValueError:
                logger.warning(f"Invalid environment variable: {env_var}")

        # Check common indicators
        if os.getenv("PYTEST_CURRENT_TEST"):
            return Environment.TESTING
        elif os.getenv("DEBUG") or os.getenv("DEV"):
            return Environment.DEVELOPMENT
        else:
            return Environment.PRODUCTION

    def _load_configuration(self):
        """Load configuration from file and environment."""
        # Load base configuration
        base_config = self._load_config_file()

        # Apply environment-specific overrides
        env_config = self._load_environment_overrides()

        # Apply environment variable overrides
        var_config = self._load_variable_overrides()

        # Merge configurations
        merged_config = self._merge_configs(base_config, env_config, var_config)

        # Validate and create typed configs
        self._validate_and_create_configs(merged_config)

    def _load_config_file(self) -> Dict[str, Any]:
        """Load configuration from JSON file.

        Returns:
            Configuration dictionary
        """
        if not self.config_file.exists():
            logger.warning(f"Configuration file not found: {self.config_file}. Using defaults.")
            return {}

        try:
            with open(self.config_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
            logger.info(f"Configuration loaded from {self.config_file}")
            return config
        except (json.JSONDecodeError, IOError) as e:
            logger.error(f"Failed to load configuration from {self.config_file}: {e}")
            return {}

    def _load_environment_overrides(self) -> Dict[str, Any]:
        """Load environment-specific configuration overrides.

        Returns:
            Environment-specific overrides
        """
        env_file = self.config_file.parent / f"config.{self.environment.value}.json"

        if not env_file.exists():
            return {}

        try:
            with open(env_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
            logger.info(f"Environment-specific configuration loaded from {env_file}")
            return config
        except (json.JSONDecodeError, IOError) as e:
            logger.error(f"Failed to load environment config from {env_file}: {e}")
            return {}

    def _load_variable_overrides(self) -> Dict[str, Any]:
        """Load configuration from environment variables.

        Returns:
            Environment variable overrides
        """
        overrides = {}

        # Define environment variable mappings
        var_mappings = {
            "DOCS_ANALYSIS_LOG_LEVEL": ("logging", "level"),
            "DOCS_ANALYSIS_LOG_FILE": ("logging", "file"),
            "DOCS_ANALYSIS_MAX_FILE_SIZE": ("analysis", "max_file_size_mb"),
            "DOCS_ANALYSIS_TIMEOUT": ("analysis", "timeout_seconds"),
            "DOCS_ANALYSIS_MEMORY_LIMIT": ("performance", "memory_limit_mb"),
            "DOCS_ANALYSIS_DB_PATH": ("database", "path"),
            "DOCS_ANALYSIS_CACHE_ENABLED": ("analysis", "cache_enabled"),
            "DOCS_ANALYSIS_OUTPUT_DIR": ("output", "output_directory"),
        }

        for var, (section, key) in var_mappings.items():
            value = os.getenv(var)
            if value is not None:
                # Convert string values to appropriate types
                converted_value = self._convert_env_var_value(value)

                if section not in overrides:
                    overrides[section] = {}
                overrides[section][key] = converted_value
                logger.debug(f"Environment variable override: {section}.{key} = {converted_value}")

        return overrides

    def _convert_env_var_value(self, value: str) -> Any:
        """Convert environment variable string to appropriate type.

        Args:
            value: String value from environment variable

        Returns:
            Converted value
        """
        # Boolean conversion
        if value.lower() in ("true", "yes", "1", "on"):
            return True
        elif value.lower() in ("false", "no", "0", "off"):
            return False

        # Numeric conversion
        try:
            if "." in value:
                return float(value)
            else:
                return int(value)
        except ValueError:
            pass

        # Return as string
        return value

    def _merge_configs(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
        """Merge multiple configuration dictionaries.

        Args:
            *configs: Configuration dictionaries to merge

        Returns:
            Merged configuration
        """
        result = {}
        for config in configs:
            result = self._deep_merge(result, config)
        return result

    def _deep_merge(self, base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
        """Deep merge two dictionaries.

        Args:
            base: Base dictionary
            override: Override dictionary

        Returns:
            Merged dictionary
        """
        result = base.copy()

        for key, value in override.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = self._deep_merge(result[key], value)
            else:
                result[key] = value

        return result

    def _validate_and_create_configs(self, config: Dict[str, Any]):
        """Validate configuration and create typed config objects.

        Args:
            config: Merged configuration dictionary
        """
        try:
            # Create typed config objects
            self.database = DatabaseConfig(**config.get("database", {}))
            self.analysis = AnalysisConfig(**config.get("analysis", {}))
            self.scoring = ScoringConfig(**config.get("scoring", {}))
            self.performance = PerformanceConfig(**config.get("performance", {}))
            self.logging = LoggingConfig(**config.get("logging", {}))
            self.ui = UIConfig(**config.get("ui", {}))
            self.security = SecurityConfig(**config.get("security", {}))
            self.output = OutputConfig(**config.get("output", {}))

            # Store raw config for access
            self._raw_config = config

            logger.info("Configuration validated and loaded successfully")

        except (TypeError, ValueError) as e:
            logger.error(f"Configuration validation failed: {e}")
            raise

    def get(self, key: str, default: Any = None) -> Any:
        """Get configuration value using dot notation.

        Args:
            key: Configuration key (e.g., "analysis.max_file_size_mb")
            default: Default value if key not found

        Returns:
            Configuration value
        """
        keys = key.split(".")
        value = self._raw_config

        try:
            for k in keys:
                value = value[k]
            return value
        except (KeyError, TypeError):
            return default

    def set(self, key: str, value: Any):
        """Set configuration value using dot notation.

        Args:
            key: Configuration key (e.g., "analysis.max_file_size_mb")
            value: Value to set
        """
        keys = key.split(".")
        config = self._raw_config

        # Navigate to parent of target key
        for k in keys[:-1]:
            if k not in config:
                config[k] = {}
            config = config[k]

        # Set the value
        config[keys[-1]] = value
        logger.debug(f"Configuration updated: {key} = {value}")

    def save_configuration(self, file_path: Union[str, Path] = None):
        """Save current configuration to file.

        Args:
            file_path: Path to save configuration (optional)
        """
        save_path = Path(file_path) if file_path else self.config_file

        try:
            # Ensure directory exists
            save_path.parent.mkdir(parents=True, exist_ok=True)

            with open(save_path, 'w', encoding='utf-8') as f:
                json.dump(self._raw_config, f, indent=2, ensure_ascii=False)

            logger.info(f"Configuration saved to {save_path}")
        except IOError as e:
            logger.error(f"Failed to save configuration to {save_path}: {e}")
            raise

    def reload_configuration(self):
        """Reload configuration from files."""
        self._config_cache.clear()
        self._load_configuration()
        logger.info("Configuration reloaded")

    def validate_configuration(self) -> List[str]:
        """Validate current configuration.

        Returns:
            List of validation errors (empty if valid)
        """
        errors = []

        # Validate paths
        if not self.database.path:
            errors.append("Database path cannot be empty")

        # Validate file sizes
        if self.analysis.max_file_size_mb <= 0:
            errors.append("Max file size must be positive")

        # Validate timeouts
        if self.analysis.timeout_seconds <= 0:
            errors.append("Timeout must be positive")

        # Validate memory limits
        if self.performance.memory_limit_mb <= 0:
            errors.append("Memory limit must be positive")

        # Validate scoring weights sum to 1.0
        weight_sum = sum(self.scoring.weights.values())
        if abs(weight_sum - 1.0) > 0.01:
            errors.append(f"Scoring weights must sum to 1.0 (current: {weight_sum})")

        # Validate output directories
        if not self.output.output_directory:
            errors.append("Output directory cannot be empty")

        # Validate log level
        valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
        if self.logging.level.upper() not in valid_log_levels:
            errors.append(f"Invalid log level: {self.logging.level}")

        return errors

    def get_environment_info(self) -> Dict[str, Any]:
        """Get environment and configuration information.

        Returns:
            Environment information dictionary
        """
        return {
            "environment": self.environment.value,
            "config_file": str(self.config_file),
            "configuration_loaded": bool(self._raw_config),
            "validation_errors": self.validate_configuration(),
            "configuration_sections": list(self._raw_config.keys()),
            "data_directory": str(Path(self.database.path).parent),
            "log_file": self.logging.file,
            "output_directory": self.output.output_directory
        }

    def create_directories(self):
        """Create necessary directories based on configuration."""
        directories = [
            Path(self.database.path).parent,
            Path(self.logging.file).parent,
            Path(self.output.output_directory),
            Path(self.output.template_directory),
        ]

        for directory in directories:
            try:
                directory.mkdir(parents=True, exist_ok=True)
                logger.debug(f"Directory ensured: {directory}")
            except OSError as e:
                logger.error(f"Failed to create directory {directory}: {e}")

    def __str__(self) -> str:
        """String representation."""
        return f"SettingsManager(env={self.environment.value}, config={self.config_file})"


# Global settings instance
_global_settings: Optional[SettingsManager] = None


def initialize_settings(
    config_file: Union[str, Path] = None,
    environment: Environment = None
) -> SettingsManager:
    """Initialize global settings manager.

    Args:
        config_file: Path to configuration file
        environment: Deployment environment

    Returns:
        SettingsManager instance
    """
    global _global_settings
    _global_settings = SettingsManager(config_file, environment)
    _global_settings.create_directories()
    return _global_settings


def get_settings() -> SettingsManager:
    """Get global settings manager instance.

    Returns:
        SettingsManager instance
    """
    if _global_settings is None:
        # Initialize with defaults
        _global_settings = SettingsManager()
    return _global_settings


def get_config(key: str, default: Any = None) -> Any:
    """Get configuration value using global settings.

    Args:
        key: Configuration key
        default: Default value

    Returns:
        Configuration value
    """
    return get_settings().get(key, default)


def set_config(key: str, value: Any):
    """Set configuration value using global settings.

    Args:
        key: Configuration key
        value: Value to set
    """
    get_settings().set(key, value)